#!/usr/bin/env python3

#
# Just a checker for CVE-2020-28018
# Exim Use-After-Free in tls-openssl.c leading to Remote Code Execution
#
#    This checker first checks if the vulnerable version (sent through the
#      banner) is in the vulnerable range or not.
#            
#    The vulnerable range is: 4.90 - 4.94.2. If the target version is included
#       in this range it might be vulnerable (if not patched).
#
#     Perhaps a target being vulnerable, this checker also checks for target
#       requirements to be met for an attacker to be able to exploit it.
#
#      Requirements:
#         - PIPELINE is enabled: Checked through the EHLO output
#         - STARTTLS is enabled: Checked through the EHLO output
#	  - X_PIPE_CONNECT is disabled: I guess there is no way to
#             confirm this remotely.
#
#      Also, the checker tries to perform a TLS connection against the target,
#        as sometimes the STARTTLS is dropped in the EHLO output but then is not
#        available.
#
#

import sys
import ssl
from socket import *

DEBUG = 0

vuln_range = [4.90, 4.942]

def connect_tls(s, host, context):
	s.send('STARTTLS\r\n'.encode('utf-8'))
	rsp = s.recv(1024).decode('utf-8')
	if(DEBUG):
		print('[DEBUG] Received: ' + rsp)
	if(rsp[:3] != '220'):
		print('[-] Expected response code 220 but %s was received instead.' % rsp[:3])
		exit()
	print('[*] Starting a TLS channel with target...')
	tls_s = context.wrap_socket(s, server_hostname=host)
	tls_s.send('EHLO CVE202028018checker\r\n'.encode('utf-8'))
	rsp = tls_s.recv(1024).decode('utf-8')
	if(DEBUG):
		print('[DEBUG] Received: ' + rsp)
	if(rsp[:3] != '250'):
		print('[-] Expected response code 250 but %s was received instead.' % rsp[:3])
		exit()
	tls_s.close()
	s.close()
	print('[*] TLS channel opened!')
	return 1
	
def check_pipelining(rsp):
	if('250-PIPELINING' in rsp):
		return 1
	return 0
	
def check_tls(rsp):
	if('250-STARTTLS' in rsp):
		return 1
	return 0
	
def get_version(rsp):
	if(not 'Exim' in rsp):
		return 0
	v = (rsp.split('Exim '))[1].split(' ')[0]
	if(len(v.split('.')) > 2):
		return float(v[0] + '.' + (v[len(v[0]):]).replace('.',''))
	return float(v)
	
def check_version(version):
	if(version >= vuln_range[0] and version <= vuln_range[1]):
		return 1
	return 0

def get_resp(s):
	g_rsp = ''
	rsp = s.recv(1024).decode('utf-8')
	if(DEBUG):
		print('[DEBUG] Received: ' + rsp)
	if(rsp[:3] != '220'):
		print('[-] Expected response code 220 but %s was received instead.' % rsp[:3])
		exit()
	g_rsp += rsp
	s.send('EHLO CVE202028018checker\r\n'.encode('utf-8'))
	rsp = s.recv(1024).decode('utf-8')
	if(DEBUG):
		print('[DEBUG] Received: ' + rsp)
	if(rsp[:3] != '250'):
		print('[-] Expected response code 250 but %s was received instead.' % rsp[:3])
		exit()
	g_rsp += rsp
	return g_rsp
	
def main():
	if(len(sys.argv) < 3):
		print("[%%] Usage: python3 %s <target ip> <target port>" % sys.argv[0])
		exit()
	
	x = 1
	
	HOST = sys.argv[1]
	PORT = int(sys.argv[2])

	s = socket(AF_INET, SOCK_STREAM)
	context = ssl._create_unverified_context()
	
	try:
		s.connect((HOST, PORT))
	except:
		print('[-] Error connecting to target...')
		exit()
	
	forced = 0
	if(len(sys.argv) > 3 and sys.argv[3] == '--force'):
		forced = 1
	
	try:
		resp = get_resp(s)
	except:
		print('[-] Something went wrong on connection with target...')
		exit()
			
	version = get_version(resp)
	
	if(version == 0):
		x = 0
		print('[i] Banner has been modified. Version could not be retrieved! ', end='')
		if(not forced):
			print('Use --force to ignore this and continue if you know your target is in the range: ', end='')
			print(vuln_range)
			exit()
		print('')

	if(not version == 0 and not check_version(version)):
		print('[-] Target version is not in the vulnerable version range. ', end='')
		if(not forced):
			print('Use --force to ignore this and continue...')
			exit()
		print('')
		
	if(not check_pipelining(resp)):
		print('[-] PIPELINING is NOT enabled. Target is not exploitable!')
		exit()
	
	if(not check_tls(resp)):
		print('[-] TLS Not enabled. Target not exploitable!')
		exit()
	

	r = connect_tls(s, HOST, context)

	if(r and x):
		print('[+] Target appears to be VULNERABLE and Exploitable!')
		return
	elif(r and not x):
		print('[+] If the target is in the vulnerable version range, then it is EXPLOITABLE!')
		return
	print('[-] Something went wrong opening a TLS channel...')
	return
		
if(__name__ == '__main__'):
	main()

